#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "proc.h"
#include "sched.h"
#include "parse.h"
#include "assert.h"


extern list *runq;
extern list *waitq;
extern list *resourceq;

#define PROC_INST_SEPARATOR ' '


/*
 *
 */
static int pid_index = 0;


/*
 *
 */
static int proc_cpu_parse(char **f, proc *p, int val, int *size, int *indx)
{
    int   ok = 1;
    int   count = 0;
    int   duration = 0;
    token t;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, "(") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = sscanf(t.text, "%d", &duration);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ")") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ";") == 0);

    if (ok) (*size) += duration * (strlen("cpu") + 1);
    if (ok && !val) {
        for (count = 0; count < duration; count++) {
            sprintf(p->text + (*indx), "%s",  "cpu");
            (*indx) += strlen("cpu");

            sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
            (*indx) += 1;
        }
        assert(indx == size);
    }
    return (ok);
}


/*
 *
 */
static int proc_sleep_parse(char **f, proc *p, int val, int *size, int *indx)
{
    int   ok = 1;
    int   duration = 0;
    int   time_len = 0;
    token t;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, "(") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = sscanf(t.text, "%d", &duration);
    if (ok)      time_len = t.length;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ")") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ";") == 0);

    if (ok) (*size) += strlen("sleep ") + time_len + 1;
    if (ok && !val) {
        sprintf(p->text + (*indx), "%s", "sleep");
        (*indx) += strlen("sleep");

        sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
        (*indx) += 1;

        sprintf(p->text + (*indx), "%d", duration);
        (*indx) += time_len;

        sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
        (*indx) += 1;

        assert(indx == size);
    }
    return (ok);
}


/*
 *
 */
static int proc_exec_parse(char **f, proc *p, int val, int *size, int *indx)
{
    int   ok = 1;
    int   prog_len = 0;
    int   prio_len = 0;
    int   prio;
    char  program[MAX_TOKEN_LENGTH];
    token t;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, "(") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = sscanf(t.text, "%s", program);
    if (ok)      prog_len = t.length;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ",") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = sscanf(t.text, "%d", &prio);
    if (ok)      prio_len = t.length;

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ")") == 0);

    if (ok) ok = get_next_token(f, &t);
    if (ok) ok = (strcmp(t.text, ";") == 0);

    if (ok) (*size) += strlen("execn") + 1 + prog_len + 1 + prio_len + 1;

    if (ok && !val) {

        sprintf(p->text + (*indx), "%s", "execn");
        (*indx) += strlen("execn");

        sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
        (*indx) += 1;

        sprintf(p->text + (*indx), "%s", program);
        (*indx) += prog_len;

        sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
        (*indx) += 1;

        sprintf(p->text + (*indx), "%d", prio);
        (*indx) += prio_len;

        sprintf(p->text + (*indx), "%c", PROC_INST_SEPARATOR);
        (*indx) += 1;

        assert(indx == size);
    }

    return ok;
}


/*
 *
 */
int proc_op(char *s)
{
    if (strcmp(s, "cpu") == 0)   return PROC_CPU;
    if (strcmp(s, "sleep") == 0) return PROC_SLEEP;
    if (strcmp(s, "execn") == 0) return PROC_EXEC;
    return PROC_UNDEF;
}


/*
 *
 */
static int proc_parse(char **f, proc *p, int validate)
{
    int size = 0;
    int indx = 0;
    int done = 0;
    int ok   = 1;
    token t;

    while (ok && !done) {
        ok = get_next_token(f, &t);
        if (ok) {
            if (t.type != C_EOF) {
                switch(proc_op(t.text)) {
                case PROC_CPU:
                    ok = proc_cpu_parse(f, p, validate, &size, &indx);
                    break;
                case PROC_SLEEP:
                    ok = proc_sleep_parse(f, p, validate, &size, &indx);
                    break;
                case PROC_EXEC:
                    ok = proc_exec_parse(f, p, validate, &size, &indx);
                    break;
                default:
                    // display some error;
                    ok = 0;
                    break;
                }
            } else {
                done = 1;
            }
        }
    }

    if (!validate) {
        /*
         * Initialize the program pointer
         */
        p->ptr = (p->text);
    }

    if (ok) {
        return (size);
    }
    return (-1);
}


/*
 *
 */
static proc *proc_new(int size)
{
    proc *p = (proc*) malloc(sizeof(proc) + size);

    if (p != NULL) {
        memset(p, 0, sizeof(proc));
        memset(p->text, PROC_INST_SEPARATOR, size);
        p->pid = pid_index++;
        p->tsize = size;
    }
    return p;
}


/*
 *
 */
int proc_cpu(proc *p)
{
    // essentially a no-op. just do the accounting and get out.

}


/*
 *
 */
int proc_exec(proc *p, char *program, int prio)
{
    FILE *file;
    proc *pnew;
    int   tsize;
    long  fsize;
    char *fbuff;
    char *fbuff_cache_1;
    char *fbuff_cache_2;
    int   rc;

    file = fopen(program, "r+");
    if (file != NULL) {
        fsize = file_size(file);
        if (fsize > 0 && fsize < MAX_FILE_SIZE) {
            fbuff = file_buffer_alloc(file);
            fbuff_cache_1 = fbuff;
            fbuff_cache_2 = fbuff;
            tsize = proc_parse(&fbuff, NULL, 1);
            if (tsize > 0) {
                pnew = proc_new(tsize);
                if (pnew != NULL) {
                    /*
                     * Everything was successful. We have verified that the
                     * program file contains a valid program (no parse errors)
                     * so we can go ahead and read it again - call proc_parse()
                     * - without the validation flag, and be sure that there
                     * will not be any errors.
                     *
                     * This second call to proc_parse() will fill out the newly
                     * created PCB's text section, after which, the PCB will be
                     * a valid program eligible for execution.  We schedule() it
                     * right here.
                     */
                    proc_parse(&fbuff_cache_1, pnew, 0);
                    schedule(pnew);
                    rc = 0;
                } else {
                    // not enough memory to allocate new process.
                    rc = -1;
                }
            } else {
                // process validation failed (syntax error in program file?)
                printf("syntax error\n");
                rc = -1;
            }
            free(fbuff_cache_2);
        } else {
            // file size if out of whack
            rc = -1;
        }
    } else {
        // supplied filename cannot be opened
        rc = -1;
    }

    return rc;
}


/*
 *
 */
int proc_sleep(proc *p, int time)
{
    list_delink(runq, p);
    p->state = SCHED_SLEEP_WAIT;
    p->timer = time+1;
    list_append(waitq, p);
}


/*
 * core function of scheduler that simulates process execution for one time
 * slice.
 */
int proc_execute(proc *p)
{
    token op;
    token t;
    char *prog;
    int   time;
    int   prio;
    int   rc;

    if (p != NULL) {
        get_next_token(&(p->ptr), &op);
        printf("[%5d] %5d | %s\n", stime, p->pid, op.text);
    } else {
        printf("[%5d] %5s | (null)\n", stime, " ");
        goto done;
    }

    switch (proc_op(op.text)) {

    case PROC_CPU:
        rc = proc_cpu(p);
        break;

    case PROC_SLEEP:
        time = get_next_int(&(p->ptr));
        rc = proc_sleep(p, time);
        break;

    case PROC_EXEC:
        get_next_token(&(p->ptr), &t);
        prog = t.text;
        time = get_next_int(&(p->ptr));
        proc_exec(p, prog, prio);
        break;
    default:
        // exception
        break;
    }


done:

    usleep(SCHED_PAUSE);
}


/*
 *
 */
void dumpproc(proc *pcb)
{

}


